import { ResultData } from "@/api/interface";
import { HEADER_KEY_SYSTEM, LOGIN_URL, SYSTEM_ID } from "@/config/config";
import { showFullScreenLoading, tryHideFullScreenLoading } from "@/config/serviceLoading";
import { ResultEnum } from "@/enums/httpEnum";
import router from "@/routers";
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import { ElMessage, ElNotification, dayjs } from "element-plus";
import { API_PREFIX, HEADER_KEY_TOKEN } from "../config/config";
import { checkStatus } from "./helper/checkStatus";
import { refreshToken } from "./modules/login";
import { PublicStore } from "@/stores/modules/public";

/**
 * pinia 错误使用说明示例
 * https://github.com/vuejs/pinia/discussions/971
 * https://github.com/vuejs/pinia/discussions/664#discussioncomment-1329898
 * https://pinia.vuejs.org/core-concepts/outside-component-usage.html#single-page-applications
 */

const config = {
	// 默认地址请求地址，可在 .env 开头文件中修改
	baseURL: API_PREFIX,
	// 设置超时时间（10s）
	timeout: ResultEnum.TIMEOUT as number,
	// 跨域时候允许携带凭证
	withCredentials: true
};
let isRefresh = false;
class RequestHttp {
	service: AxiosInstance;
	public constructor(config: AxiosRequestConfig) {
		// 实例化axios
		this.service = axios.create(config);
		/**
		 * @description 请求拦截器
		 * 客户端发送请求 -> [请求拦截器] -> 服务器
		 * token校验(JWT) : 接受服务器返回的token,存储到vuex/pinia/本地储存当中
		 */
		this.service.interceptors.request.use(
			(config: any) => {
				const publicStore = PublicStore();
				// * 如果当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { headers: { noLoading: true } }来控制不显示loading，参见loginApi
				config.headers!.noLoading || showFullScreenLoading();
				let param = config.params || config.data;
				config.headers[HEADER_KEY_SYSTEM] = param?.[HEADER_KEY_SYSTEM] || SYSTEM_ID;
				config.headers[HEADER_KEY_TOKEN] = publicStore.token;
				return { ...config, headers: config.headers };
			},
			(error: AxiosError) => {
				return Promise.reject(error);
			}
		);

		/**
		 * @description 响应拦截器
		 *  服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
		 */
		this.service.interceptors.response.use(
			async (response: AxiosResponse) => {
				const publicStore = PublicStore();
				const { data } = response;
				// * 在请求结束后，并关闭请求 loading
				tryHideFullScreenLoading();
				// * 登陆失效（code == 599）
				if (data.code == ResultEnum.NO_AUTH || data.code == ResultEnum.OVERDUE) {
					ElNotification.warning({
						title: "登录失效",
						message: "用户登录状态已失效，请重新登录"
					});
					publicStore.setToken("");
					// router.replace(LOGIN_URL);
					const param = {} as any;
					if (router.currentRoute.value.fullPath != "/" || router.currentRoute.value.fullPath != LOGIN_URL) {
						param.redirect = encodeURIComponent(router.currentRoute.value.fullPath);
					}
					router.replace({ path: LOGIN_URL, query: param });
					return Promise.reject(data);
				}
				// * 全局错误信息拦截（防止下载文件得时候返回数据流，没有code，直接报错）
				if (data.code && data.code !== ResultEnum.SUCCESS) {
					// ElMessage.error(data.msg || data.message);
					// return Promise.reject(data);
				}
				let diff = dayjs().diff(dayjs(publicStore.expire), "minute");
				if (0 > diff && diff > -10 && !isRefresh && publicStore.token) {
					isRefresh = true;
					const { result } = await refreshToken();
					publicStore.setToken(result.token);
					publicStore.setExpire(result.expire);
					isRefresh = false;
				}
				if (response.config.responseType == "blob") {
					console.log(response);
					return { data: data, filename: response.headers["file-name"] };
				} else {
					return data;
				}
			},
			async (error: AxiosError) => {
				const { response } = error;
				tryHideFullScreenLoading();
				// 请求超时单独判断，因为请求超时没有 response
				if (error.message.indexOf("timeout") !== -1) ElMessage.error("请求超时！请您稍后重试");
				// 根据响应的错误状态码，做不同的处理
				let ok = checkStatus(response?.status);
				if (!ok) {
					return Promise.reject(error);
				}
				// 服务器结果都没有返回(可能服务器错误可能客户端断网)，断网处理:可以跳转到断网页面
				if (!window.navigator.onLine) router.replace("/500");
				return Promise.reject(error);
			}
		);
	}

	// * 常用请求方法封装
	get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
		return this.service.get(url, { params, ..._object });
	}
	post<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
		return this.service.post(url, params, _object);
	}
	put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
		return this.service.put(url, params, _object);
	}
	delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
		return this.service.delete(url, { params, ..._object });
	}
	downloadGet(url: string, params?: object, _object = {}): Promise<{ filename: string; data: BlobPart }> {
		return this.service.get(url, { params, ..._object });
	}
	// download(url: string, params?: object, _object = {}): Promise<BlobPart> {
	// 	return this.service.post(url, params, _object);
	// }
}

export default new RequestHttp(config);
